package com.mh.utils;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.mh.constant.WxPayJsApiConstant;
import com.mh.constant.WxPayJsApiHeaderConstant;
import com.mh.constant.WxPayJsApiOtherConstant;
import com.mh.model.WxPayJsApiAmountBO;
import com.mh.model.WxPayJsApiOrderBO;
import com.mh.model.WxPayJsApiPreVO;
import com.mh.model.WxPayPayerBO;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.RequiredArgsConstructor;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.stereotype.Component;

import java.io.*;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;


/**
 * Date:2023/9/5
 * author:zmh
 * description:JSAPI工具类
 **/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class WxPayJsApiHttpUtil {

    private static Logger logger = LoggerFactory.getLogger(WxPayJsApiHttpUtil.class);
    private final WxPayJsApiSignUtil wxPayJsApiSignUtil;
    /**
     * appId
     **/
    @Value("${wxPay.appId}")
    String appId;

    /**
     * openId
     **/
    @Value("${wxPay.openId}")
    String openId;
    /**
     * mchId
     **/
    @Value("${wxPay.jsApi.mchId}")
    String mchId;
    /**
     * mchSerialNo
     **/
    @Value("${wxPay.jsApi.mchSerialNo}")
    String mchSerialNo;
    /**
     * apiV3Key
     **/
    @Value("${wxPay.jsApi.apiV3Key}")
    String apiV3Key;
    /**
     * notifyUrl
     **/
    @Value("${wxPay.jsApi.notifyUrl}")
    String notifyUrl;

    /**
     * 微信JS_API下单
     */
    public WxPayJsApiPreVO jsApiV3(Integer total) throws Exception {
        //请求URL
        HttpPost httpPost = new HttpPost(WxPayJsApiConstant.JS_API_URL);
        // 生成模拟系统内部订单号（yyyyMMddHHmmssSSS）
        String out_trade_no = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN);
        // 构造请求body参数
        WxPayJsApiAmountBO amount = new WxPayJsApiAmountBO(); // 金额相关对象
        amount.setTotal(total);
        amount.setCurrency("CNY");

        WxPayPayerBO payer = new WxPayPayerBO();
        payer.setOpenid(openId);

        WxPayJsApiOrderBO request = new WxPayJsApiOrderBO();
        request.setMchid(mchId);
        request.setOut_trade_no(out_trade_no);
        request.setAttach("我的测试下单");
        request.setAppid(appId);
        request.setDescription("唔巴阁组件套餐");
        request.setNotify_url(notifyUrl);
        request.setAmount(amount);
        request.setPayer(payer);

        StringEntity entity = new StringEntity(JSONUtil.toJsonStr(request), "utf-8");
        entity.setContentType("application/json");

        // 将请求参数和请求头设置上
        httpPost.setEntity(entity);
        httpPost.setHeader(WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT, WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT_VAL);

        // 调用自定义方法，构建请求信息
        CloseableHttpClient closeableHttpClient = initCloseableHttpClientV3();

        // 完成请求，获取响应数据
        CloseableHttpResponse response = closeableHttpClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());

        // 关闭资源
        response.close();
        closeableHttpClient.close();

        // 构造响应信息 （调用自定义方法，将预支付订单号传入，获取需要返回给前端的数据）-- 参数：prepay_id
        WxPayJsApiPreVO result = convertWxPayJsApiPreInfo((String) JSONUtil.parseObj(bodyAsString).get(WxPayJsApiOtherConstant.HTTP_OTHER_PREPAY_ID));
        return result;
    }

    /**
     * 获取 - jsApi加密串（签名）及前端换起JSAPI支付界面需要的参数
     * @param prepayId 预支付订单号
     */
    protected WxPayJsApiPreVO convertWxPayJsApiPreInfo(String prepayId) throws Exception {
        logger.info("[开始]预支付信息-prepayId : {}", prepayId);
        // 构建 - 相关参数（时间戳，随机字符串，预支付订单号）
        String time = String.valueOf(System.currentTimeMillis() / 1000);
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String packageStr = StrUtil.format(WxPayJsApiConstant.SIGN_PACKAGE_STR, prepayId);
        // 构建 - 签名信息
        ArrayList<String> list = new ArrayList<>();
        list.add(appId);
        list.add(time);
        list.add(nonceStr);
        list.add(packageStr);

        // 操作 - 签名
        String packageSign = wxPayJsApiSignUtil.signV3(WxPayJsApiSignUtil.buildSignMessage(list).getBytes());

        // 构建 - 响应信息
        WxPayJsApiPreVO result = new WxPayJsApiPreVO();
        result.setWxAppId(appId);
        result.setWxTimeStamp(time);
        result.setWxNonceStr(nonceStr);
        result.setWxPackage(packageStr);
        result.setWxSignType(WxPayJsApiConstant.SIGN_TYPE);
        result.setWxPaySign(packageSign);
        logger.info("[结束]预支付信息-WxPayJsApiPreVO : {}", JSONUtil.toJsonStr(result));
        return result;
    }

    /**
     * 查看 -根据微信支付订单号查询支付订单信息
     * @param transaction_id 微信支付订单号
     * @return ·
     */
    public JSONObject queryJsApiOrderInfoV3(String transaction_id) throws HttpCodeException, GeneralSecurityException, NotFoundException, IOException, URISyntaxException {
        String url = StrUtil.format(WxPayJsApiConstant.TRANSACTIONS_ORDER_QUERY_URL, transaction_id, mchId);
        URIBuilder uriBuilder = new URIBuilder(url);
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader(WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT, WxPayJsApiHeaderConstant.HTTP_HEADER_ACCEPT_VAL);

        CloseableHttpClient httpClient = initCloseableHttpClientV3();
        CloseableHttpResponse response = httpClient.execute(httpGet);

        String bodyAsString = EntityUtils.toString(response.getEntity());
        System.out.println("bodyAsString:" + bodyAsString);
        return JSONUtil.parseObj(bodyAsString);
    }

    /**
     * 获取证书目录
     * @return ·
     */
    protected static String getResourcesPath() {
        // 这里需要注意的是ApplicationHome是属于SpringBoot的类
        // 获取项目下resources/static/img路径
        ApplicationHome applicationHome = new ApplicationHome(WxPayJsApiHttpUtil.class);

        // 保存目录位置根据项目需求可随意更改 - win和linux目录不一样
        String path = applicationHome.getDir().getParentFile().getParentFile().getAbsolutePath() + "/src/main/resources/pay/";
        System.out.println("path:" + path);
        return path;
    }

    /**
     * 构造通用CloseableHttpClient（定时更新平台证书功能），用于发送请求
     * @return ·
     */
    protected CloseableHttpClient initCloseableHttpClientV3() throws IOException, HttpCodeException, GeneralSecurityException, NotFoundException {
        // 获取 - 秘钥证书信息
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(WxPayJsApiHttpUtil.getResourcesPath() + "apiclient_key.pem"));
        // 获取 - 证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();

        // 操作 - 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
                new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));

        // 操作 - 从证书管理器中获取verifier
        Verifier verifier = certificatesManager.getVerifier(mchId);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier));

        // 构造通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签，并进行证书自动更新
        return builder.build();
    }
}
